package it.unibo.refolding.pico.mfp;

import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.bytes.ByteList;
import it.unimi.dsi.fastutil.bytes.ByteLists;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import it.unimi.dsi.fastutil.doubles.DoubleLists;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.google.common.base.Preconditions;

class IgorUtil {
  static final int MAXDIMS = 4;
  static final int POINTER_SIZE = 4;
  static final int MAX_UNIT_CHARS = 3;

  static final int NT_CMPLX = 1; // Complex numbers.
  static final int NT_FP32 = 2; // 32 bit fp numbers.
  static final int NT_FP64 = 4; // 64 bit fp numbers.
  static final int NT_I8 = 8; // 8 bit signed integer. Requires Igor Pro 2.0 or
                              // later.
  static final int NT_I16 = 0x10; // 16 bit integer numbers. Requires Igor Pro
                                  // 2.0 or later.
  static final int NT_I32 = 0x20; // 32 bit integer numbers. Requires Igor Pro
                                  // 2.0 or later.
  static final int NT_UNSIGNED = 0x40; // Makes above signed integers unsigned.
                                       // Requires Igor Pro 3.0 or later.
  /*
   * When dwell time is at lest 10 seconds, a separate file is created.
   */
  public static final double TIME_THRESHOLD = 10;

  /**
   * Advances exactly maxLength + 1 positions. For the String the bytes are
   * taken one by one, but for checksum computation they are read a shorts,
   * according to the byte ordering.
   */
  public static Checksummed<ByteList> readString(int maxLength, ByteBuffer bb) {
    Preconditions.checkArgument((maxLength & 1) == 1);
    byte[] a = new byte[maxLength + 1];
    bb.get(a, 0, a.length);
    int i;
    for (i = 0; i <= maxLength && a[i] != 0; ++i) {
    }
    Preconditions.checkArgument(i != maxLength + 1, "Could not find end of string.");
    int unsignedChecksum = 0;
    ByteBuffer b = ByteBuffer.wrap(a);
    b.order(bb.order());
    for (int j = (maxLength >> 1) + 1; --j >= 0;) {
      unsignedChecksum = addShort(b.getShort(), unsignedChecksum);
    }
    assert b.remaining() == 0;
    return new Checksummed<>(
      ByteLists.unmodifiable(ByteArrayList.wrap(a, i)), (short) unsignedChecksum);
  }

  public static IntList readInts(int n, ByteBuffer bb) {
    int[] a = new int[n];
    for (int i = 0; i < a.length; ++i) {
      a[i] = bb.getInt();
    }
    return IntLists.unmodifiable(IntArrayList.wrap(a));
  }

  public static DoubleList readDoubles(int n, ByteBuffer bb) {
    double[] a = new double[n];
    for (int i = 0; i < a.length; ++i) {
      a[i] = bb.getDouble();
    }
    return DoubleLists.unmodifiable(DoubleArrayList.wrap(a));
  }

  public static Checksummed<List<ByteList>> readStrings(int maxdims, int maxUnitChars, ByteBuffer bb) {
    ByteList[] a = new ByteList[maxdims];
    int unsignedChecksum = 0;
    for (int i = 0; i < maxdims; ++i) {
      Checksummed<ByteList> c = readString(maxUnitChars, bb);
      a[i] = c.object;
      unsignedChecksum += c.unsignedChecksum;
    }
    return new Checksummed<>(
      Collections.unmodifiableList(Arrays.asList(a)), (short) unsignedChecksum);
  }

  public static String unsignedBytesToString(ByteList unsignedBname) {
    char[] a = new char[unsignedBname.size()];
    for (int i = 0; i < a.length; ++i) {
      a[i] = (char) unsignedBname.getByte(i);
    }
    return String.valueOf(a, 0, a.length);
  }

  public static long unsignedIntToLong(int unsignedInt) {
    return unsignedInt & 0xFFFFFFFFL;
  }

  public static List<String> unsignedBytesListToStringList(List<ByteList> in) {
    String[] a = new String[in.size()];
    int i = 0;
    for (ByteList bl : in) {
      a[i++] = unsignedBytesToString(bl);
    }
    return Collections.unmodifiableList(Arrays.asList(a));
  }

  public static ByteList stringToUnsignedBytes(String string) {
    char[] a = string.toCharArray();
    byte[] b = new byte[a.length];
    for (int i = 0; i < a.length; ++i) {
      b[i] = (byte) a[i];
    }
    return ByteLists.unmodifiable(ByteArrayList.wrap(b));
  }

  public static List<ByteList> stringListToUnsignedBytesList(List<String> in) {
    ByteList[] a = new ByteList[in.size()];
    int i = 0;
    for (String bl : in) {
      a[i++] = stringToUnsignedBytes(bl);
    }
    return Collections.unmodifiableList(Arrays.asList(a));
  }

  static int addInts(IntList a, int unsignedChecksum) {
    for (int x : a) {
      unsignedChecksum = IgorUtil.addInt(x, unsignedChecksum);
    }
    return unsignedChecksum;
  }

  static int addInt(int x, int unsignedChecksum) {
    unsignedChecksum += x;
    // Either signed or unsigned is fine.
    unsignedChecksum += (x >>>= 16);
    return unsignedChecksum;
  }

  static int addLong(long x, int unsignedChecksum) {
    unsignedChecksum += x;
    // Either signed or unsigned is fine.
    unsignedChecksum += (x >>>= 16);
    unsignedChecksum += (x >>>= 16);
    unsignedChecksum += (x >>>= 16);
    return unsignedChecksum;
  }

  static int addDouble(double x, int unsignedChecksum) {
    return addLong(Double.doubleToRawLongBits(x), unsignedChecksum);
  }

  static int addShort(short checksum, int unsignedChecksum) {
    unsignedChecksum += checksum;
    return unsignedChecksum;
  }

  static int skip(int byteCount, int unsignedChecksum, ByteBuffer bb) {
    int check = skip(byteCount, bb);
    return unsignedChecksum + check;
  }

  private static int skip(int byteCount, ByteBuffer bb) {
    Preconditions.checkArgument((byteCount & 1) == 0, byteCount);
    int n = byteCount >> 1;
    int unsignedChecksum = 0;
    for (int i = 0; i < n; ++i) {
      short s = bb.getShort();
      unsignedChecksum += s;
    }
    return unsignedChecksum;
  }

  public static int addDoubles(DoubleList sfA, int unsignedChecksum) {
    for (double d : sfA) {
      unsignedChecksum = addDouble(d, unsignedChecksum);
    }
    return unsignedChecksum;
  }

  static int numBytesPerPoint(short type) {
    int numBytesPerPoint;

    // Consider the number type, not including the complex bit or the unsigned
    // bit.
    switch (type & ~(NT_CMPLX | NT_UNSIGNED)) {
    case NT_I8:
      numBytesPerPoint = 1; // char
      break;
    case NT_I16:
      numBytesPerPoint = 2; // short
      break;
    case NT_I32:
      numBytesPerPoint = 4; // long
      break;
    case NT_FP32:
      numBytesPerPoint = 4; // float
      break;
    case NT_FP64:
      numBytesPerPoint = 8; // double
      break;
    default:
      throw new IllegalArgumentException("type=" + type);
    }
    if ((type & NT_CMPLX) != 0) {
      numBytesPerPoint *= 2; // Complex wave - twice as many points.
    }
    return numBytesPerPoint;
  }
}
